package chc

import (
	"database/sql"
	"fmt"
	"reflect"
	"time"
)

type RawSet struct {
	query string
	args  []interface{}
	// orm   *orm
}

func newRawSet(query string, args ...interface{}) *RawSet {
	return &RawSet{
		query: query,
		args:  args,
	}
}

func (raw *RawSet) SetArgs(args ...interface{}) *RawSet {
	raw.args = args
	return raw
}

func (raw *RawSet) Exec() (sql.Result, error) {
	args := getFlatParams(nil, raw.args, DefaultTimeLoc)
	if nil != args {
		return chSession.Connect.Exec(raw.query, args)
	} else {
		return chSession.Connect.Exec(raw.query)
	}
}

func (raw *RawSet) ExecAll() (err error) {
	args := getFlatParams(nil, raw.args, DefaultTimeLoc)
	for _, conn := range chSession.SingleConns {
		var e error
		if nil != args {
			_, e = conn.Exec(raw.query, args)
		} else {
			_, e = conn.Exec(raw.query)
		}
		if nil != e {
			err = e
		}
	}

	return
}

func (raw *RawSet) RowsToMap(result *Params, keyCol string, valueCol string) (int64, error) {
	return raw.queryRowsTo(result, keyCol, valueCol)
}

func (raw *RawSet) RowsToMaps(container *[]map[string]interface{}, keys ...string) (int64, error) {
	return raw.queryRowsToMaps(container, keys...)
}

func (raw *RawSet) queryRowsTo(container interface{}, keyCol, valueCol string) (int64, error) {
	var (
		maps Params
		ind  *reflect.Value
	)

	var typ int
	switch container.(type) {
	case *Params:
		typ = 1
	default:
		typ = 2
		vl := reflect.ValueOf(container)
		id := reflect.Indirect(vl)
		if vl.Kind() != reflect.Ptr || id.Kind() != reflect.Struct {
			panic(fmt.Errorf("<RawSeter> RowsTo unsupport type `%T` need ptr struct", container))
		}

		ind = &id
	}

	query := raw.query
	args := getFlatParams(nil, raw.args, DefaultTimeLoc)

	rs, err := chSession.Connect.Query(query, args...)
	if err != nil {
		return 0, err
	}

	defer func() {
		_ = rs.Close()
	}()

	var (
		refs []interface{}
		cnt  int64
		cols []string
	)

	var (
		keyIndex   = -1
		valueIndex = -1
	)

	for rs.Next() {
		if cnt == 0 {
			columns, err := rs.Columns()
			if err != nil {
				return 0, err
			}
			cols = columns
			refs = make([]interface{}, len(cols))
			for i := range refs {
				if keyCol == cols[i] {
					keyIndex = i
				}
				if typ == 1 || keyIndex == i {
					var ref sql.NullString
					refs[i] = &ref
				} else {
					var ref interface{}
					refs[i] = &ref
				}
				if valueCol == cols[i] {
					valueIndex = i
				}
			}
			if keyIndex == -1 || valueIndex == -1 {
				panic(fmt.Errorf("<RawSeter> RowsTo unknown key, value column name `%s: %s`", keyCol, valueCol))
			}
		}

		if err := rs.Scan(refs...); err != nil {
			return 0, err
		}

		if cnt == 0 {
			switch typ {
			case 1:
				maps = make(Params)
			}
		}

		if nil == refs {
			panic(ErrEmpty)
		}

		key := reflect.Indirect(reflect.ValueOf(refs[keyIndex])).Interface().(sql.NullString).String

		switch typ {
		case 1:
			value := reflect.Indirect(reflect.ValueOf(refs[valueIndex])).Interface().(sql.NullString)
			if nil == maps {
				panic(ErrEmpty)
			}
			if value.Valid {
				maps[key] = value.String
			} else {
				maps[key] = nil
			}

		default:
			if nil == ind {
				panic(ErrEmpty)
			}
			if id := ind.FieldByName(camelString(key)); id.IsValid() {
				raw.setFieldValue(id, reflect.ValueOf(refs[valueIndex]).Elem().Interface())
			}
		}

		cnt++
	}

	if typ == 1 {
		v, _ := container.(*Params)
		*v = maps
	}

	return cnt, nil
}

func (raw *RawSet) queryRowsToMaps(container *[]map[string]interface{}, keys ...string) (int64, error) {

	query := raw.query
	args := getFlatParams(nil, raw.args, DefaultTimeLoc)

	rs, err := chSession.Connect.Query(query, args...)
	if err != nil {
		return 0, err
	}

	defer func() {
		_ = rs.Close()
	}()

	var (
		refs []interface{}
		cols []string
	)

	for rs.Next() {
		if nil == refs || 1 > len(cols) {
			cols, err = rs.Columns()
			if err != nil {
				return 0, err
			}
			refs = make([]interface{}, len(cols))
			for i := range refs {
				var ref interface{}
				refs[i] = &ref
			}
		}

		if err := rs.Scan(refs...); err != nil {
			return 0, err
		}

		maps := make(map[string]interface{})
		for index, col := range cols {
			if 1 < len(keys) && 0 > indexOf(arrStringToI(keys), col) {
				continue
			}

			maps[col] = refs[index]
		}

		*container = append(*container, maps)
	}

	return int64(len(*container)), nil
}

func (raw *RawSet) setFieldValue(ind reflect.Value, value interface{}) {
	switch ind.Kind() {
	case reflect.Bool:
		if value == nil {
			ind.SetBool(false)
		} else if v, ok := value.(bool); ok {
			ind.SetBool(v)
		} else {
			v, _ := StrTo(ToStr(value)).Bool()
			ind.SetBool(v)
		}

	case reflect.String:
		if value == nil {
			ind.SetString("")
		} else {
			ind.SetString(ToStr(value))
		}

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if value == nil {
			ind.SetInt(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				ind.SetInt(val.Int())
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				ind.SetInt(int64(val.Uint()))
			default:
				v, _ := StrTo(ToStr(value)).Int64()
				ind.SetInt(v)
			}
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if value == nil {
			ind.SetUint(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				ind.SetUint(uint64(val.Int()))
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				ind.SetUint(val.Uint())
			default:
				v, _ := StrTo(ToStr(value)).Uint64()
				ind.SetUint(v)
			}
		}
	case reflect.Float64, reflect.Float32:
		if value == nil {
			ind.SetFloat(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Float64:
				ind.SetFloat(val.Float())
			default:
				v, _ := StrTo(ToStr(value)).Float64()
				ind.SetFloat(v)
			}
		}

	case reflect.Struct:
		if value == nil {
			ind.Set(reflect.Zero(ind.Type()))
			return
		}
		switch ind.Interface().(type) {
		case time.Time:
			var str string
			switch d := value.(type) {
			case time.Time:
				TimeFromDB(&d, DefaultTimeLoc)
				ind.Set(reflect.ValueOf(d))
			case []byte:
				str = string(d)
			case string:
				str = d
			}
			if str != "" {
				if len(str) >= 19 {
					str = str[:19]
					t, err := time.ParseInLocation(formatDateTime, str, DefaultTimeLoc)
					if err == nil {
						t = t.In(DefaultTimeLoc)
						ind.Set(reflect.ValueOf(t))
					}
				} else if len(str) >= 10 {
					str = str[:10]
					t, err := time.ParseInLocation(formatDate, str, DefaultTimeLoc)
					if err == nil {
						ind.Set(reflect.ValueOf(t))
					}
				}
			}
		case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
			indi := reflect.New(ind.Type()).Interface()
			sc, ok := indi.(sql.Scanner)
			if !ok {
				return
			}
			err := sc.Scan(value)
			if err == nil {
				ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
			}
		}

	case reflect.Ptr:
		if value == nil {
			ind.Set(reflect.Zero(ind.Type()))
			break
		}
		ind.Set(reflect.New(ind.Type().Elem()))
		raw.setFieldValue(reflect.Indirect(ind), value)
	}
}
